Add pagination example#333
Open
nikitamelni wants to merge 11 commits into
Open
Conversation
✅ Deploy Preview for context-edge canceled.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Bare-bone copy of the App Router starter to host two pagination demonstrations in separate follow-up commits. README describes the tradeoffs and the two demo routes; no behavioural changes yet. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Server component reads `limit` from `context.dynamicInputs`, slices its
source data, and only the visible window is rendered. A client "Load
more" button does router.replace('?limit=…', { scroll: false }) inside
useTransition for soft-navigation re-render with no page reload.
The corresponding Uniform composition '01 - Datasource Pagination' is
already authored at /:locale/pagination-datasource, with `limit` declared
as an allowed query string on the project map node (default 5). That
declaration is what lets the value survive middleware buildRoutePath and
reach the Route API as a dynamic input.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Client component `PaginationContainer` uses `getUniformSlot({ slot })` to
read its `cards` slot as an array, slices it with a useState counter, and
exposes a Load more button. Card is a small server component that renders
title/description with UniformText.
Adapted from the pages-router `wrapperComponent={({ items }) => …}` pattern —
the App Router SDK's UniformSlot is per-child, so the array-style slice
lives in the component body via getUniformSlot.
The corresponding Uniform composition '02 - Pagination Container' is
already authored at /:locale/pagination-slot with 11 card instances.
Tradeoff (documented in README): because the container is a client
component, all slot children get serialized into the RSC payload up front.
Load more is instant and there's no server round-trip, but the unrevealed
items are still shipped. Pick this when the slot is small/bounded; pick
approach #1 when the tail is large enough to matter.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The starter's `rewriteRequestPath` was passing only `url.pathname`, so
`?limit=10` was being stripped before the SDK ever saw it. The Route API
then returned the project map node's default `limit=5`, and the page
appeared frozen at 5 items.
Verified end-to-end: with the path including `url.search`, the Route API
returns `dynamicInputs: { limit: "<url value>", locale: "en" }`. The
project map node already had `data.queryStrings: [{ name: "limit",
value: "5" }]` correctly stored by MCP — the bug was purely in this
example's middleware glue.
package-lock.json is also updated to reflect the package rename from
the scaffolding commit (this happens automatically on `npm install`).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switch PaginationContainer from cumulative reveal to page-at-a-time: - `defaultLimit` is now the page size; only items in the current page window are visible, and moving forward replaces them (older items disappear from view, no growing list). - Replace single Load more button with Prev / Next + page indicator. - `loadMoreText` is repurposed as the Next-button label (set to "Next →" on the composition in Uniform via MCP). - `step` parameter kept in the type for backwards compatibility but is unused in page mode. README rewritten for both approaches: - Approach #1: callout about the middleware `url.search` requirement so others don't trip on the same issue. - Approach #2: copy reflects page-at-a-time behaviour (not append), with the tradeoff note still calling out that all cards are shipped upfront because PaginationContainer is a client component. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Match Approach #2's UX so the two demos differ only in *where* the slicing happens, not in how the user navigates. - Project map node updated via Canvas API: `data.queryStrings` is now `[{ name: "page", value: "1" }]` (was `limit`). - `paginatedList.tsx` reads `context.dynamicInputs.page`, computes the slice `[(page-1)*PAGE_SIZE, page*PAGE_SIZE)` from 47 items, and renders only that page. Page size is a constant in code. - `loadMore.tsx` is replaced by `paginationControls.tsx` — Prev / Next buttons that call `router.replace('?page=N', { scroll: false })` inside `useTransition`, with disabled states at the ends. - README rewritten for the new flow; gotcha callout about `url.search` in middleware kept. Verified against the Route API: `?page=3` → `dynamicInputs.page="3"`; no query → default `page="1"`. Each distinct `?page=N` is a separate cache entry in the Route fetch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…iformSlot
The composition has been restructured in Uniform to a dynamic project map
node `/:locale/pagination-datasource/:offset`, with a $loop in the
paginatedList's `cards` slot bound to a `Query Blog Entry Content`
data resource (queryBlogEntry data type) whose `offset` variable picks
up the dynamic input and `limit` is fixed to 5. The Route API now
returns exactly the 5-item window per page.
Verified against the Route API:
- offset=0 → 5 cards (Demo Blog Post 1, 10, 2, 3, 4)
- offset=5 → 5 cards (Demo Blog Post 5..9)
- offset=8 → 2 cards (partial last page)
- offset=10 → 0 cards (past the end)
Code changes:
- paginatedList.tsx: drop the 47-item SOURCE_DATA constant and the
slicing logic. Render `slots.cards` directly via UniformSlot. Read
`context.dynamicInputs.offset` only to derive the current page for
the controls.
- paginationControls.tsx: switch from `?page=N` query strings to a path
segment — `router.replace(`${basePath}/${page}`, { scroll: false })`.
Next is disabled when the partial slot tells us we're on the last page.
- lib/paginationDatasource.ts (new): shared `PAGE_SIZE = 5`,
`rewritePaginationDatasourcePath()`, and `offsetToPage()` helpers so
the middleware and the component can't drift.
- middleware.ts: middleware's `rewriteRequestPath` now translates the
visitor-facing path `/pagination-datasource/<page>` into the
offset-based path Uniform expects, before forwarding to the SDK.
The only computation in TypeScript is `(page - 1) * PAGE_SIZE` and its
inverse — all data shaping happens in Uniform.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…demos Two follow-ups while wiring up Approach #1's data resource: 1. Empty page → page 1. `rewritePaginationDatasourcePath` now also rewrites `/en/pagination-datasource` and `/en/pagination-datasource/` to `/en/pagination-datasource/0`, so a bare URL renders the first page instead of 404-ing on the `:offset` project map node. The marker-match logic also guards against accidental matches like `/pagination-datasource-other/...`. 2. Shared Prev / Next UI. `paginationControls.tsx` is now a small, presentational client component (`hasPrev`, `hasNext`, `onPrev`, `onNext`, `pending`, indicator via `children`). Both demos use it: - Approach #1 wraps it in `routerPagination.tsx`, which holds `useRouter` + `useTransition` and turns the callbacks into soft navigations. Indicator is `Page {currentPage}`. - Approach #2 (`paginationContainer.tsx`) calls `PaginationControls` directly and wires the callbacks to `useState`. Indicator is `Page {n} of {total}`. The unused `loadMoreText` UniformText is dropped — both buttons now use fixed labels for visual parity. Smoke-tested the rewriter against bare paths, page numbers, trailing slashes, anchors, and a near-miss prefix (`-extra/3`) — all expected. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Make the Prev / Next button labels configurable from Uniform (per locale, with inline-edit support via UniformText) instead of hardcoding them in the React tree, and remove the back-compat parameters that no longer do anything. Uniform side (via MCP): - paginatedList component: added `previousLabel`, `nextLabel` text params (localizable). Composition #1 sets them to "← Previous" / "Next →". - paginationContainer component: removed `step` and `loadMoreText`, added `previousLabel`, `nextLabel`. Composition #2 sets them likewise. Stale `step` / `loadMoreText` values on the existing composition instance were also cleared (MCP couldn't reset values for params that no longer exist on the definition — fell back to one Canvas API PUT for that single cleanup, then verified via MCP). - titleParameter on paginationContainer switched from `loadMoreText` to `nextLabel` so the canvas tree still shows a sensible instance title. Code side: - PaginationControls: drop the fixed "← Previous" / "Next →" strings. New required `previousLabel` / `nextLabel` ReactNode props are rendered inside each button. - routerPagination + paginatedList + paginationContainer: pass `<UniformText component={component} parameter={label} as="span" />` for each label, with a string fallback if the parameter is absent. - paginationContainer.tsx: drop the `step` and `loadMoreText` props from the type as well as the unused branches that consumed them. The component is now strictly a paged slot wrapper. Composition #2 also now uses the same `Query Blog Entry Content` data resource as #1 (window = 50, no offset), so the two demos differ only in *where* the slicing happens — README updated to reflect this. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pulled via `npm run uniform:pull` after the Approach #1 / #2 build-out: - New component definitions: paginatedList, paginationContainer, card - Updated page component (cards / paginationContainer added to content slot allowedComponents) - Two compositions (`01 - Datasource Pagination`, `02 - Pagination Container`) - Dynamic project map nodes: `/:locale/pagination-datasource/:offset` (composition #1) and `/:locale/pagination-slot` (composition #2) - Data types backing the blog content: blogEntry, blogEntryList, queryBlogEntry; `helloWorld` removed from the project - Blog `contentType` plus 10 demo `entry` records that feed the data resource both demos paginate through Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A customer cloning this example needs to push the committed `uniform-data/` into their own (potentially empty) Uniform project before running the dev server. The previous Getting Started only mentioned `uniform:pull`, which only makes sense after the content is already in the project. New flow: prepare project → env → install → push → manifest → dev. `uniform:pull` is still mentioned, as a follow-up tip for syncing UI edits back into git. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bd0eb29 to
72b7ce2
Compare
davetayls
approved these changes
Jun 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.